// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Diagnostics;
using System.Globalization;
using System.Text;

using Microsoft.TeamFoundation.Migration.Toolkit;

namespace Microsoft.TeamFoundation.Migration.Toolkit.Wit
{
    /// <summary>
    /// Mapper class; provides access to session data from the internal database.
    /// </summary>
    class Mapper
    {
        private SystemPair<IMigrationWorkItemStore> m_stores;   // Pair of stores
        private int m_sessionId;                            // Session id
        private SystemPair<int> m_sourceId;                 // Source ids
        private SystemPair<string> m_highWatermark;         // Watermarks 
        private bool m_inverse;

        /// <summary>
        /// Gets the session id.
        /// </summary>
        public int SessionId { get { return m_sessionId; } }

        /// <summary>
        /// Returns sources used by the session.
        /// </summary>
        public SystemPair<int> SourceId { get { return m_sourceId; } }

        /// <summary>
        /// Returns high water marks for the session.
        /// </summary>
        public SystemPair<string> HighWatermark { get { return m_highWatermark; } }
        
        /// <summary>
        /// Creates mapper object.
        /// </summary>
        /// <param name="stores">Stores</param>
        public Mapper(
            SystemPair<IMigrationWorkItemStore> stores)
        {
            m_stores = stores;

            using (MigrationSqlTransaction trn = (MigrationSqlTransaction)DataAccessManager.Current.StartTransaction())
            {
                using (SqlCommand cmd = trn.CreateCommand())
                {
                    cmd.CommandText = "prc_iiFindAddSession";
                    cmd.CommandType = CommandType.StoredProcedure;
                    cmd.Parameters.Add("@LeftSystem", SqlDbType.NVarChar, SqlData.SystemNameLength).Value = stores.Tfs.SystemName;
                    cmd.Parameters.Add("@LeftSource", SqlDbType.NVarChar, SqlData.SourceNameLength).Value = stores.Tfs.StoreName;
                    cmd.Parameters.Add("@RightSystem", SqlDbType.NVarChar, SqlData.SystemNameLength).Value = stores.Other.SystemName;
                    cmd.Parameters.Add("@RightSource", SqlDbType.NVarChar, SqlData.SourceNameLength).Value = stores.Other.StoreName;

                    using (SqlDataReader reader = cmd.ExecuteReader())
                    {
                        reader.Read();

                        m_sessionId = (int)reader["SessionId"];
                        Pair<int> sourceId = new Pair<int>(
                            (int)reader["LeftSourceId"],
                            (int)reader["RightSourceId"]);
                        Pair<string> hwm = new Pair<string>(
                            (reader["LeftWatermark"] == DBNull.Value) ? null : reader["LeftWatermark"].ToString(),
                            (reader["RightWatermark"] == DBNull.Value) ? null : reader["RightWatermark"].ToString());
                        m_inverse = (bool)reader["Inverse"];

                        Side tfsSide = m_inverse ? Side.Right : Side.Left;
                        m_sourceId = new SystemPair<int>(sourceId[tfsSide], sourceId[!tfsSide]);
                        m_highWatermark = new SystemPair<string>(hwm[tfsSide], hwm[!tfsSide]);
                    }
                }
                trn.Complete();
            }
        }

        /// <summary>
        /// For every given item on the source system returns id of its reflection, or null if the
        /// reflection does not exist.
        /// </summary>
        /// <param name="sourceSystem">Source system</param>
        /// <param name="ids">Ids of items in the source system</param>
        /// <returns>Ids of items in the target system</returns>
        public string[] FindReflections(
            SystemType sourceSystem,
            string[] ids)
        {
            string[] results = new string[ids.Length];

            if (ids.Length > 0)
            {
                using (SqlConnection conn = DataAccessManager.Current.GetSqlConnection())
                {
                    conn.Open();
                    using (SqlCommand cmd = new SqlCommand())
                    {
                        cmd.Connection = conn;
                        cmd.CommandType = CommandType.Text;

                        StringBuilder q = new StringBuilder();

                        q.AppendLine("SELECT swi.PublicId, twi.PublicId");
                        q.AppendLine("FROM ItemPairs ip");
                        q.AppendLine("  INNER JOIN WorkItems swi ON ip.SourceId=swi.Id");
                        q.AppendLine("  INNER JOIN WorkItems twi ON ip.TargetId=twi.Id");
                        q.AppendLine("WHERE ip.SessionId=@SessionId AND swi.SourceId=@SourceId");
                        q.Append("  AND swi.PublicId IN (");

                        for (int i = 0; i < ids.Length; i++)
                        {
                            string paramName = string.Format(CultureInfo.InvariantCulture, "@P{0}", i);
                            if (i > 0)
                            {
                                q.Append(',');
                            }

                            q.Append(paramName);
                            cmd.Parameters.Add(paramName, SqlDbType.NVarChar, SqlData.WorkItemIdLength).Value = ids[i];
                        }

                        q.Append(')');

                        cmd.Parameters.Add("@SessionId", SqlDbType.Int).Value = m_sessionId;
                        cmd.Parameters.Add("@SourceId", SqlDbType.Int).Value = m_sourceId[sourceSystem];

                        cmd.CommandText = q.ToString();

                        using (SqlDataReader reader = cmd.ExecuteReader())
                        {
                            StringComparer cmp = m_stores[sourceSystem].IdComparer;
                            while (reader.Read())
                            {
                                string srcId = (string)reader[0];
                                string dstId = (string)reader[1];

                                for (int i = 0; i < ids.Length; i++)
                                {
                                    if (cmp.Equals(srcId, ids[i]))
                                    {
                                        results[i] = dstId;
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return results;
        }

        /// <summary>
        /// Sets the watermark for the given system.
        /// </summary>
        /// <param name="system">System type</param>
        /// <param name="watermark">Watermark</param>
        internal void SaveWatermark(
            SystemType system,
            string watermark)
        {
            using (MigrationSqlTransaction trn = (MigrationSqlTransaction)DataAccessManager.Current.StartTransaction())
            {
                using (SqlCommand cmd = trn.CreateCommand())
                {
                    Side dbSide = GetDbSide(system);
                    cmd.CommandType = CommandType.Text;
                    cmd.CommandText = string.Format(
                        CultureInfo.InvariantCulture,
                        "UPDATE Sessions SET {0}=@Watermark WHERE Id=@SessionId",
                        dbSide == Side.Left? "LeftWatermark" : "RightWatermark");

                    object val = watermark;

                    if (val == null)
                    {
                        val = DBNull.Value;
                    }

                    cmd.Parameters.Add("@Watermark", SqlDbType.NVarChar, SqlData.SessionWatermarkLength).Value = val;
                    cmd.Parameters.Add("@SessionId", SqlDbType.Int).Value = m_sessionId;

                    cmd.ExecuteNonQuery();
                }
                trn.Complete();
            }

            // Update cached watermark
            m_highWatermark[system] = watermark;
        }

        /// <summary>
        /// Gets side where data for the given system are stored in the database.
        /// </summary>
        /// <param name="type">System type</param>
        /// <returns>Side</returns>
        private Side GetDbSide(
            SystemType type)
        {
            Side s = type == SystemType.Tfs ? Side.Left : Side.Right;
            if (m_inverse)
            {
                s = !s;
            }
            return s;
        }
    }
}
